﻿(*
#r @"C:\Users\Administrator\Desktop\Ferrari\HyperF\HyperF\bin\Debug\HyperF.dll"
#r "System.Management"
*)
module HyperF.HyperF

open System



let private localServer = System.Net.Dns.GetHostName().ToUpper()

let private isLocal (server:string) = server.ToUpper() = localServer


/// <summary>
/// Contact the remote Windows Service HyperF module to execute locally an HyperF function.
/// This is a workaround for the functions that require special permissions.
/// </summary>
let private execute_remote_HyperF_function server (functionInfo:string array) =
  let client = new System.Net.Sockets.TcpClient(server, 17300)
  try
    use stream = client.GetStream()
    use sw = new System.IO.StreamWriter(stream)
    use sr = new System.IO.StreamReader(stream)
    functionInfo |> Array.iter (fun x -> sw.WriteLine(x)) 
    sw.Flush()
    let result = System.Boolean.Parse(sr.ReadLine())
    if not(result) then failwith (sr.ReadLine())
    else ()
  finally
    client.Close()

/// <summary>
/// Contact the remote Windows Service HyperF module to execute locally an HyperF function.
/// Very similar to execute_remote_HyperF_function but this returns an object as result
/// </summary>
let private execute_remote_HyperF_function_withReturn server (functionInfo:string array) =
  let client = new System.Net.Sockets.TcpClient(server, 17300)
  try
    use stream = client.GetStream()
    use sw = new System.IO.StreamWriter(stream)
    use sr = new System.IO.StreamReader(stream)
    functionInfo |> Array.iter (fun x -> sw.WriteLine(x)) 
    sw.Flush()
    let result = System.Boolean.Parse(sr.ReadLine())  
    if not(result) then failwith (sr.ReadLine())
    else sr.ReadLine()
  finally
    client.Close()



//
//------------------------------------------------------------------
//------------------------------------------------------------------
//------------------------------------------------------------------
//

(* --------------------------------------------------------- *)
(* # Functions for Managing Virtual Hard disk (VHD) files  # *)


/// <summary>
/// Gets the default path for the VHD images of the specified HyperV server
/// </summary>
/// <param name="server">The HyperV hostname. None to indicate local computer</param>
let get_VhdDefaultPath server = WMIInterface.get_VhdDefaultPath server



// Moved in LocalOnly module:
//let new_VHD_Differencing server path parentPath =
//let new_VHD_Dynamic server path maxInternalSize =
//let new_VHD_Fixed server path maxInternalSize =
//let merge_VHD server sourcePath destinationPath =
//let get_VHDInfo server (vhdPath:string) =

  

(* ---------------------------------------------------- *)
(* # Functions for managing VM information / status   # *)


/// <summary>
/// #Example: get_VM None "4ad2b64d-6bb8-4f6a-b52e-9aab39064a0f"
/// #	      // Returns a single WMI MsVM_ComputerSystem object for the VM named with that guid
/// </summary>
let get_VM server guid =
  try
    let vmobj = WMIInterface.get_VM server guid
    new VirtualMachine(vmobj)
  with _ ->
    failwithf @"Virtual machine not found"

/// <summary>
/// #Example: get_VMByName None "Virtual Computation Node 01"
/// #	      // Returns a single WMI MsVM_ComputerSystem object for the VM named "Virtual Computation Node 01" of the local HyperV server
/// </summary>
let get_VMByName server vmName =
  try
    let vmobj = WMIInterface.get_VMByName server vmName
    new VirtualMachine(vmobj)
  with _ ->
    failwithf @"Virtual machine not found"


/// <summary>
/// #Example: get_AllVM (Some "REALNODE02")
/// #	      // Returns all the VM present in the REALNODE02 HyperV server
/// #Example: get_AllVM (Some "REALNODE02") |> Array.iter (fun x -> printfn "%s" (x.["ElementName"])
/// #	      // Prints the name of all the VM present in the REALNODE02 HyperV server
/// </summary>
let get_AllVM server =
  let allvm = WMIInterface.get_AllVM server
  allvm |> Array.map (fun x -> new VirtualMachine(x))


let get_VMSummary2 (vm:VirtualMachine) =
  let summobj = WMIInterface.get_VMSummary (Some vm.Server) vm.Name [|0;1;2;3;4;100;101;102;103;104;105;106;108;109;110;111|]
  new VirtualMachineSummary(vm.ManagementObject_VM, summobj)

let get_VMSummary server vmName =
  let vmobj = WMIInterface.get_VM server vmName
  get_VMSummary2 (new VirtualMachine(vmobj))


let get_VMIntegrationServicesInfo (vm:VirtualMachine) =
  let kvpobj = try WMIInterface.get_VMKvpExchangeComponent vm.ManagementObject_VM
               with _ -> failwith "Integration services not available"
  new VMIntegrationServicesInfo(kvpobj)


  
let get_AllVMSummary server =
  get_AllVM server |> Array.map (fun x -> get_VMSummary2 x)


let get_VMEnabledVideo (vm:VirtualMachine) =
  let (o1,o2) = WMIInterface.get_VMEnabledVideo vm.ManagementObject_VM
  new DisplayController(o1, o2)


/// <summary>
/// Gets the current screen image of the specified Virtual Machine and 
/// returns it as a thumbnail.
/// This method returns the image in raw RGB 565 format.
/// </summary>
let get_VMThumbnailImage (vm:VirtualMachine) width height =
  try WMIInterface.get_VMThumbnailImage vm.ManagementObject_VM width height
  with _ -> null


/// <summary>
/// Starts the specified Virtual Machine
///
/// #Example: start_VM None "Windows XP sp3"
/// #         // Starts the "Windows XP sp3" VM on the local HyperV server
/// </summary>
let start_VM (vm:VirtualMachine) = WMIInterface.start_VM vm.ManagementObject_VM

/// <summary>
/// Saves the current state and suspends the specified Virtual Machine
/// </summary>
let suspend_VM (vm:VirtualMachine) = WMIInterface.suspend_VM vm.ManagementObject_VM

/// <summary>
/// Turns off the specified Virtual Machine
/// </summary>
let stop_VM (vm:VirtualMachine) = WMIInterface.stop_VM vm.ManagementObject_VM

/// <summary>
/// Pause the specified Virtual Machine
/// </summary>
let pause_VM (vm:VirtualMachine) = WMIInterface.pause_VM vm.ManagementObject_VM

/// <summary>
/// Hard reset the specified Virtual Machine
/// </summary>
let hardReset_VM (vm:VirtualMachine) = WMIInterface.hardReset_VM vm.ManagementObject_VM

/// <summary>
/// Shutdowns (sending the command to the OS) the specified Virtual Machine
/// </summary>
let shutdown_VM (vm:VirtualMachine) force reason =
  WMIInterface.shutdown_VM vm.ManagementObject_VM force reason


(* -------------------------------------------------------------------------------------- *)
(* # Functions for working with VMs and their motherboard settings (inc CPU and Memory) # *)


// --> Moved in LocalOnly module
//let new_VM server newVmName =
//let new_VM_To server newVmName path =


/// <summary>
/// Changes the name for an existing virtual machine
///
/// #Example: set_VM_Name None "Windows XP sp2" "Windows XP sp3"
/// #         // Changes the name for the VM "Windows XP sp2" with the new name "Windows XP sp3"
/// </summary>
let change_VM_Name (vm:VirtualMachine) newVmName =
  WMIInterface.change_VM_Name vm.ManagementObject_VM newVmName
  
/// Sets the VM Notes attribute
let change_VM_Notes (vm:VirtualMachine) newNotes = 
  WMIInterface.change_VM_Notes vm.ManagementObject_VM newNotes


/// <summary>
/// Removes the specified Virtual machine 
/// NOTE: This function does not delete the hard disks associated
/// </summary>
let private remove_VM (vm:VirtualMachine) =
  WMIInterface.remove_VM vm.ManagementObject_VM


/// <summary>
/// Sets the new memory amount setting for the specified VM
/// </summary>
/// <param name="vm">The virtual machine</param>
/// <param name="newMemoryAmountMb">The new memory amount in MegaBytes</param>
let set_VMMemory (vm:VirtualMachine) (newMemoryAmountMb:int) =
  WMIInterface.set_VMMemory vm.ManagementObject_VM (int64 newMemoryAmountMb)


/// <summary>
/// Sets the new cpu amount setting for the specified VM
/// TODO: sets the number of single-core processors or the number of core for 1 processor?
/// </summary>
/// <param name="vm">The virtual machine</param>
/// <param name="newCpuCount">The new number of CPU</param>
let set_VMCPUCount (vm:VirtualMachine) newCpuCount =
  WMIInterface.set_VMCPUCount vm.ManagementObject_VM newCpuCount


(* ---------------------------------------------------------------------------- *)
(* # Functions for working with disk objects , SCSI Controller, Driver, Disk  # *)


let get_VMDiskControllers (vm:VirtualMachine) =
  let controllers = WMIInterface.get_VMDiskControllers vm.ManagementObject_VM
  controllers |> Array.map (fun x -> new DiskController(x))

let get_VMDiskIDEControllers (vm:VirtualMachine) =
  let controllers = WMIInterface.get_VMDiskIDEControllers vm.ManagementObject_VM
  controllers |> Array.map (fun x -> new DiskController(x))

let get_VMDiskSCSIControllers (vm:VirtualMachine) =
  let controllers = WMIInterface.get_VMDiskSCSIControllers vm.ManagementObject_VM
  controllers |> Array.map (fun x -> new DiskController(x))


let get_VMDrivesByController (controller:DiskController) =
  let drives = WMIInterface.get_VMDrivesByController controller.ManagementObject
  [| for d in drives -> new DiskDrive(d :?> System.Management.ManagementObject) |]

  
let get_VMDiskByDrive (drive:DiskDrive) =
  try
    let disk = WMIInterface.get_VMDiskByDrive drive.ManagementObject
    Some( new Disk(disk) )
  with _ -> None


let get_VMDiskByController controller address =
  let drives = get_VMDrivesByController controller
  let drive = try drives |> Array.find (fun x -> x.Address = address)
              with _ -> failwith "There is no drive in the specified Address"
  match get_VMDiskByDrive drive with
  | Some(x) -> x
  | None -> failwith "There is no disk in the specified Address"

let get_VMDisksByController (controller:DiskController) =
  [| for n in 0..(int(controller.Limit)-1) do
       let f = fun () -> try Some(get_VMDiskByController controller n) with _ -> None
       match f() with Some(x) -> yield x | None -> () |]

let get_VMDisks (vm:VirtualMachine) =
  let controllers = get_VMDiskControllers vm
  [| for c in controllers do yield! get_VMDisksByController c |]
  

let add_VMSCSIController (vm:VirtualMachine) =
  WMIInterface.add_VMSCSIController vm.ManagementObject_VM

let remove_VMSCSIcontroller (controller:DiskController) =
  WMIInterface.remove_VMSCSIcontroller controller.ManagementObject

let add_VMDVDDriveToController (controller:DiskController) address =
  let drive = WMIInterface.add_VMDVDDriveToController controller.ManagementObject address
  new DiskDrive(drive)
  
let add_VMDiskDriveToController (controller:DiskController) address =
  let drive = WMIInterface.add_VMDiskDriveToController controller.ManagementObject address
  new DiskDrive(drive)

let get_VmNameByDiskController (controller:DiskController) =
  WMIInterface.get_VMNameByDiskController controller.ManagementObject
 

//# Similar to Adding the drive, but we request a different resource type,
//  and the parent is the 'Microsoft Synthetic Disk Drive', instead of disk controller
//# Mount an ISO in a DVD drive or A VHD in a Disk drive 
// --> Moved in LocalOnly module
//let add_VMDVDDiskToController (controller:DiskController) isoPath =
//let add_VMHardDiskToController (controller:DiskController) vhdPath =


/// <summary>
/// Removes the specified DiskDrive from its VirtualMachine DiskController
/// </summary>
let remove_VMDrive (drive:DiskDrive) =
  let controller = drive.DiskController
  let vmName = get_VmNameByDiskController controller
  let vmobj = WMIInterface.get_VMByName (Some controller.Server) vmName
  match (get_VMDiskByDrive drive) with
  | Some(d) -> WMIInterface.remove_VMDrive vmobj d.ManagementObject
  | None -> ()
  WMIInterface.remove_VMDrive vmobj drive.ManagementObject

// <summary>
// Removes the specified Disk from its VirtualMachine DiskDrive
// </summary>
// This function works. I removed it because I want that people use remove_VMDrive.
//let remove_VMDisk (disk:Disk) =
//  let controller = disk.DiskController
//  WMIInterface.remove_VMDrive (Some controller.Server) (controller.GetVMName()) disk.ManagementObject


// --> Moved in LocalOnly module
//let add_VMFloppyDisk


let remove_VMFloppyDisk (vm:VirtualMachine) =
  WMIInterface.remove_VMFloppyDisk vm.ManagementObject_VM


let get_VMFloppyDisk (vm:VirtualMachine) =
  let floppy = WMIInterface.get_VMFloppyDisk vm.ManagementObject_VM
  let conn = floppy.["Connection"] :?> string array
  conn.[0]



(* ------------------------------------------------------------------------------------------------------ *)
(* # Functions for working with Networking, (NICS, switches and ports on switches that nics connect to) # *)


let private wmiVMSwitchType (vmSwitch:System.Management.ManagementObject) =
  let t = WMIInterface.get_VMSwitchType vmSwitch
  match t with
  | 0 -> NetworkSwitchType.Private
  | 1 -> NetworkSwitchType.Internal
  | _ -> NetworkSwitchType.External


/// <summary>
/// Gets all the Virtual Network for the specified HyperV server
/// </summary>
let get_AllVMSwitch server =
  let switches = WMIInterface.get_AllVMSwitch server
  switches |> Array.map (fun x -> new NetworkSwitch(x, wmiVMSwitchType(x)))

/// <summary>
/// Gets the specified Virtual Network in the specified HyperV server
/// </summary>
let get_VMSwitch server switchName =
  let switch = WMIInterface.get_VMSwitch server switchName
  new NetworkSwitch(switch, wmiVMSwitchType(switch))

/// <summary>
/// Gets all the Virtual Network Interface Cards for the specified Virtual Machine
/// TODO: test the switches, I don't know if they works correctly
/// <summary>
/// <param name="vm">The virtual machine</param>
/// <param name="getLegacy">true to include legacy adapters in results</param>
/// <param name="getVmBus">true to include non-legacy adapters in results</param>
let get_VMNic (vm:VirtualMachine) getLegacy getVmBus =
  let nics = WMIInterface.get_VMNic vm.ManagementObject_VM getLegacy getVmBus
  nics |> Array.map (fun x -> new NetworkIC(x))


/// <summary>
/// Gets the Switch used by the specified NIC
/// </summary>
let get_VMNicSwitch (nic:NetworkIC) =
  let sw = WMIInterface.get_VMNicSwitch nic.ManagementObject
  new NetworkSwitch(sw, wmiVMSwitchType(sw))



let get_IpAddressByMac macAddress =
  let table = IpHlpApi.getIpNetTable()
  let entry = table |> Array.find (fun x -> x.MacAddress = macAddress)
  entry.IpAddress


let get_ExternalEthernetDevices server =
  let cards = WMIInterface.get_ExternalEthernets server
  [| for c in cards -> new ExternalEthernetDevice(c :?> System.Management.ManagementObject) |]

let get_ExternalEthernetDevice server deviceName =
  let card = WMIInterface.get_ExternalEthernet server deviceName
  new ExternalEthernetDevice(card)

let get_VMSwitchFromExternalEthernetDevice (externalEthernetDevice:ExternalEthernetDevice) =
  let sw = WMIInterface.get_VMSwitch_from_externalEthernetDevice (externalEthernetDevice.ManagementObject)
  if sw <> null then new NetworkSwitch(sw, wmiVMSwitchType(sw))
  else failwith "Not found"



// TODO: Add check to validate the mac address!
/// <summary>
/// Adds a Virtual Network Interface Card in the specified Virtual Machine
/// </summary>
/// <param name="vm">The Virtual Machine</param>
/// <param name="virtualSwitch">The Virtual Network setting used by this NIC; None to sets the NIC "Not Connected"</param>
/// <param name="isLegacy">True to create a legacy NIC; False otherwise</param>
/// <param name="macAddress">A MAC address statically assigned to the NIC; None to assign a dynamic MAC Address</param>
let add_VMNic (vm:VirtualMachine) (switch:NetworkSwitch option) isLegacy (macAddress:string option) =
  match switch with
  | None -> WMIInterface.add_VMNic vm.ManagementObject_VM None isLegacy macAddress
  | Some(s) -> WMIInterface.add_VMNic vm.ManagementObject_VM (Some s.ManagementObject) isLegacy macAddress
  
/// <summary>
/// Sets a Virtual Network setting to the specified NIC in the specified Virtual Machine
/// </summary>
/// <param name="vm">The virtual machine</param>
/// <param name="nic">The Virtual Network Interface Card</param>
/// <param name="virtualSwitch">The Virtual Newtork setting to use; None to set the NIC "Not Connected"</param>
let set_VMNicSwitch (vm:VirtualMachine) (nic:NetworkIC) (switch:NetworkSwitch option) =
  match switch with
  | None -> WMIInterface.set_VMNicSwitch vm.ManagementObject_VM nic.ManagementObject None
  | Some(s) -> WMIInterface.set_VMNicSwitch vm.ManagementObject_VM nic.ManagementObject (Some s.ManagementObject)

// TODO: Add check to validate the mac address!
let set_VMNicAddress (vm:VirtualMachine) (nic:NetworkIC) (macAddress:string) =
  WMIInterface.set_VMNicAddress vm.ManagementObject_VM nic.ManagementObject macAddress

let remove_VMNic (vm:VirtualMachine) (nic:NetworkIC) =
  WMIInterface.remove_VMNic vm.ManagementObject_VM nic.ManagementObject 

let get_VMNicByMACaddress server macAddress =
  let nic = WMIInterface.get_VMNicByMACaddress server macAddress
  new NetworkIC(nic)

let get_VMByMACaddress server macAddress =
  let vm = WMIInterface.get_VMByMACaddress server macAddress
  new VirtualMachine(vm)


let new_VMPrivateSwitch server virtualSwitchName =
  let sw = WMIInterface.new_VMPrivateSwitch server virtualSwitchName 1024
  new NetworkSwitch(sw, wmiVMSwitchType(sw))

let new_VMInternalSwitch server virtualSwitchName =
  let sw = WMIInterface.new_VMInternalSwitch server virtualSwitchName 1024
  new NetworkSwitch(sw, wmiVMSwitchType(sw))

let new_VMExternalSwitch server (externalEthernetDevice:ExternalEthernetDevice) virtualSwitchName =
  let sw = WMIInterface.new_VMExternalSwitch server (externalEthernetDevice.ManagementObject) virtualSwitchName 1024
  new NetworkSwitch(sw, wmiVMSwitchType(sw))


(* ------------------------------------------------------------- *)
(* # Functions for managing VM State (Snapshots and VM Export) # *)
//
// --> Moved in LocalOnly module







//
// *************************************************************
// Functions which can be called only from the same computer
// of the queried HyperV server
//

module LocalOnly =

  let new_VHD_Differencing path parentPath =
    WMIInterface.new_VHD_Differencing None path parentPath

  /// <summary>
  /// Creates a new virtual hard disk, dinamically expanded, in the specified HyperV server, path and with the specified size
  ///
  /// #Example: let path = ((get_VhdDefaultPath None) + @"\tenby.vhd")
  ///           new_VHD_Dynamic None path 20
  /// #         // Creates a 20GB Dynamic VHD named "tenby.vhd" in the default VHD folder
  /// </summary>
  let new_VHD_Dynamic path (sizeInGB:uint32) =
    let maxInternalSize = (uint64 sizeInGB) * 0x40000000UL      // Size1G = 0x40000000
    WMIInterface.new_VHD_Dynamic None path maxInternalSize

  /// <summary>
  /// Creates a new virtual fixed-size hard disk in the specified HyperV server, path and with the specified size
  ///
  /// #Example: new_VHD_Fixed None ((get_VhdDefaultPath None) + @"\tenby.vhd") 20
  /// #         // Creates a 20GB Fixed VHD named "tenby.vhd" in the default VHD folder
  /// </summary>
  let new_VHD_Fixed path (sizeInGB:uint32) =
    let maxInternalSize = (uint64 sizeInGB) * 0x40000000UL      // Size1G = 0x40000000
    WMIInterface.new_VHD_Fixed None path maxInternalSize
    
  let merge_VHD sourcePath destinationPath =
    WMIInterface.merge_VHD None sourcePath destinationPath

  /// <summary>
  /// #Example: (get_VHDInfo None @"C:\Users\Public\Documents\Microsoft Hyper-V\Virtual Hard Disks\Core.vhd").["ParentPath"]
  /// #         // Returns the parent path of core.vhd e.g. "C:\Users\Public\Documents\Microsoft Hyper-V\Virtual Hard Disks\Brand-new-core.vhd"
  /// </summary>
  let get_VHDInfo vhdPath =
    let xml = WMIInterface.get_VHDInfo None vhdPath
    new VhdInfo(vhdPath, xml)
  
  
  let mount_VHD vhdPath =
    WMIInterface.mount_VHD None vhdPath
  
  let unmount_VHD vhdPath =
    WMIInterface.unmount_VHD None vhdPath
  

  /// <summary>
  /// Creates a new virtual machine with the default settings:
  /// One single-core processor, 512Mb of RAM, no Hard disk, no NIC, no other stuff
  ///
  /// #Example: new_VM (Some "REALNODE03") "Virtual Compute Node 01"
  /// #         // Creates a new VM called "Virtual Compute Node 01" in REALNODE03 HyperV server
  /// </summary>
  let new_VM newVmName =
    let vmobj = WMIInterface.new_VM None newVmName null
    new VirtualMachine(vmobj)
    
  /// <summary>
  /// Creates a new virtual machine with the default settings, saving it in the specified path.
  ///
  /// #Example: new_VM_To (Some "REALNODE03") "Virtual Compute Node 01" @"\\REALNODE08\VirtualMachines"
  /// #         // Creates a new VM called "Virtual Compute Node 01" in REALNODE03 HyperV server,
  /// #         // and stores it in \\REALNODE08\VirtualMachines
  /// </summary>
  let new_VM_To newVmName path =
    let vmobj = WMIInterface.new_VM None newVmName path
    new VirtualMachine(vmobj)

  /// <summary>
  /// Connect an emulated Dvd disk to the specified controller.
  /// </summary>
  let add_VMDVDDiskToController (controller:DiskController) isoPath =
    if not( isLocal controller.Server ) then failwith "This method is local-only"
    WMIInterface.add_VMDVDDiskToController controller.ManagementObject isoPath

  /// <summary>
  /// Connect an emulated Hard disk to the specified drive.
  /// </summary>
  let add_VMHardDiskToDrive (drive:DiskDrive) vhdPath =
    if not( isLocal drive.Server ) then failwith "This method is local-only"
    WMIInterface.add_VMHardDiskToDrive drive.ManagementObject vhdPath


  // Moved below
  //let add_VMHardDiskToController (controller:DiskController) vhdPath =


  let add_VMFloppyDisk vmName vfdPath =
    let vm = WMIInterface.get_VMByName None vmName
    WMIInterface.add_VMFloppyDisk vm vfdPath

  
  /// <summary>
  /// Exports the specified Virtual Machine to the specified path.
  /// The path where the VM will be exported can be a network resource.
  /// NOTE: Beginning with Windows Server 2008 R2 this method is deprecated.
  /// </summary>
  let export_VM vmName exportDirectory copyVmState =
    let vm = WMIInterface.get_VMByName None vmName
    WMIInterface.export_VM vm exportDirectory copyVmState

  let exportEx_VM vmName exportDirectory copyVmStorage copyVmRuntimeInformation =
    let vm = WMIInterface.get_VMByName None vmName
    WMIInterface.exportEx_VM vm exportDirectory copyVmStorage copyVmRuntimeInformation


  /// <summary>
  /// Imports an exported Virtual Machine from the specified path.
  /// NOTE: Beginning with Windows Server 2008 R2 this method is deprecated.
  /// </summary>
  let import_VM importDirectory generateNewID =
    WMIInterface.import_VM None importDirectory generateNewID
    
  let importEx_VM newVmName generateNewID importDirectory createCopy rootDirectoryToCopy =
    WMIInterface.importEx_VM None newVmName generateNewID importDirectory createCopy rootDirectoryToCopy


  /// <summary>
  /// Deletes a Virtual Machine.
  /// If the deleteVhd param is True, deletes the VHD files too.
  /// Returns a string array which represent the VHD paths not deleted
  /// (returns an empty array if all VHD was deleted)
  /// </summary>
  let delete_VM virtualMachine deleteVhd =
    let vmDisk = get_VMDisks virtualMachine
    let vhdToDelete = [| for disk in vmDisk do yield! disk.Connection |]
    //-
    remove_VM virtualMachine
    if deleteVhd then
      let foo s:string = try System.IO.File.Delete(s); null
                         with _ -> s
      [| for file in vhdToDelete do let s = foo file in if s <> null then yield s |]
    else
      vhdToDelete





//
// ******************************************************************************
// The same LocalOnly functions. Here they select if call the LocalOnly function
// or ask to the RemoteExecuter to execute locally the function
//


let get_IpAddressByNic (nic:NetworkIC) =
  if isLocal nic.Server then
    get_IpAddressByMac nic.MacAddress
  else
    let ret = execute_remote_HyperF_function_withReturn nic.Server [| "get_IpAddressByMac"; nic.MacAddress |]
    System.Net.IPAddress.Parse(ret)



let new_VHD_Differencing server path parentPath =
  match server with
  | None -> LocalOnly.new_VHD_Differencing path parentPath
  | Some(s) when isLocal s -> LocalOnly.new_VHD_Differencing path parentPath
  | Some(s) -> execute_remote_HyperF_function s [| "new_VHD_Differencing"; path; parentPath |]


/// <summary>
/// Creates a new virtual hard disk, dinamically expanded, in the specified HyperV server, path and with the specified size
///
/// #Example: let path = ((get_VhdDefaultPath None) + @"\tenby.vhd")
///           new_VHD_Dynamic None path 20
/// #         // Creates a 20GB Dynamic VHD named "tenby.vhd" in the default VHD folder
/// </summary>
let new_VHD_Dynamic server path sizeInGB =
  match server with
  | None -> LocalOnly.new_VHD_Dynamic path sizeInGB
  | Some(s) when isLocal s -> LocalOnly.new_VHD_Dynamic path sizeInGB
  | Some(s) -> execute_remote_HyperF_function s [| "new_VHD_Dynamic"; path; string sizeInGB |]

/// <summary>
/// Creates a new virtual fixed-size hard disk in the specified HyperV server, path and with the specified size
///
/// #Example: new_VHD_Fixed None ((get_VhdDefaultPath None) + @"\tenby.vhd") 20
/// #         // Creates a 20GB Fixed VHD named "tenby.vhd" in the default VHD folder
/// </summary>
let new_VHD_Fixed server path sizeInGB =
  match server with
  | None -> LocalOnly.new_VHD_Fixed path sizeInGB
  | Some(s) when isLocal s -> LocalOnly.new_VHD_Fixed path sizeInGB
  | Some(s) -> execute_remote_HyperF_function s [| "import_VM"; path; string sizeInGB |]


let merge_VHD server sourcePath destinationPath =
  match server with
  | None -> LocalOnly.merge_VHD sourcePath destinationPath
  | Some(s) when isLocal s -> LocalOnly.merge_VHD sourcePath destinationPath
  | Some(s) -> execute_remote_HyperF_function s [| "merge_VHD"; sourcePath; destinationPath |]


/// <summary>
/// #Example: (get_VHDInfo None @"C:\Users\Public\Documents\Microsoft Hyper-V\Virtual Hard Disks\Core.vhd").["ParentPath"]
/// #         // Returns the parent path of core.vhd e.g. "C:\Users\Public\Documents\Microsoft Hyper-V\Virtual Hard Disks\Brand-new-core.vhd"
/// </summary>
let get_VHDInfo server vhdPath =
  match server with
  | None -> LocalOnly.get_VHDInfo vhdPath
  | Some(s) when isLocal s -> LocalOnly.get_VHDInfo vhdPath
  | Some(s) -> 
      let ret = execute_remote_HyperF_function_withReturn s [| "get_VHDInfo"; vhdPath |]
      VhdInfo.Deserialize(ret)



let get_mountedVHD_PartitionsLetter server vhdPath =
  try
    let partitions = WMIInterface.get_DiskPartitions server vhdPath
    let logicalDisks = partitions |> Array.map (fun x -> new System.Management.ManagementObject(string x.["Dependent"]))
    [| for ld in logicalDisks do yield string ld.["DeviceID"] |]
  with _ ->
    failwith "Mounted disk not found"


let mount_VHD server vhdPath =
  match server with
  | None -> LocalOnly.mount_VHD vhdPath
  | Some(s) when isLocal s -> LocalOnly.mount_VHD vhdPath
  | Some(s) -> execute_remote_HyperF_function s [| "mount_VHD"; vhdPath |]
  
let unmount_VHD server vhdPath =
  match server with
  | None -> LocalOnly.unmount_VHD vhdPath
  | Some(s) when isLocal s -> LocalOnly.unmount_VHD vhdPath
  | Some(s) -> execute_remote_HyperF_function s [| "unmount_VHD"; vhdPath |]


let add_VMFloppyDisk (vm:VirtualMachine) vfdPath =
  if isLocal vm.Server then
    LocalOnly.add_VMFloppyDisk vm.Name vfdPath
  else
    execute_remote_HyperF_function vm.Server [| "add_VMFloppyDisk"; vm.Name; vfdPath |]



/// <summary>
/// Creates a new virtual machine with the default settings:
/// One single-core processor, 512Mb of RAM, no Hard disk, no NIC, no other stuff
///
/// #Example: new_VM (Some "REALNODE03") "Virtual Compute Node 01"
/// #         // Creates a new VM called "Virtual Compute Node 01" in REALNODE03 HyperV server
/// </summary>
let new_VM server newVmName =
  match server with
  | None -> LocalOnly.new_VM newVmName
  | Some(s) when isLocal s -> LocalOnly.new_VM newVmName
  | Some(s) -> let ret = execute_remote_HyperF_function_withReturn s [| "new_VM"; newVmName |]
               VirtualMachine.Deserialize(ret)


/// <summary>
/// Creates a new virtual machine with the default settings, saving it in the specified path.
///
/// #Example: new_VM_To (Some "REALNODE03") "Virtual Compute Node 01" @"\\REALNODE08\VirtualMachines"
/// #         // Creates a new VM called "Virtual Compute Node 01" in REALNODE03 HyperV server,
/// #         // and stores it in \\REALNODE08\VirtualMachines
/// </summary>
let new_VM_To server newVmName path =
  match server with
  | None -> LocalOnly.new_VM_To newVmName path
  | Some(s) when isLocal s -> LocalOnly.new_VM_To newVmName path
  | Some(s) -> let ret = execute_remote_HyperF_function_withReturn s [| "new_VM_To"; newVmName; path |]
               VirtualMachine.Deserialize(ret)


/// <summary>
/// Connect an emulated Dvd disk to the specified controller.
/// </summary>
let add_VMDVDDiskToController (controller:DiskController) isoPath =
  if isLocal controller.Server then LocalOnly.add_VMDVDDiskToController controller isoPath
  else
    let vmName = get_VmNameByDiskController controller
    execute_remote_HyperF_function controller.Server [| "add_VMDVDDiskToController"; vmName; controller.Name; isoPath |]

/// <summary>
/// Connect an emulated Hard disk to the specified drive.
/// </summary>
let add_VMHardDiskToDrive (drive:DiskDrive) vhdPath =
  if isLocal drive.Server then LocalOnly.add_VMHardDiskToDrive drive vhdPath
  else
    let controller = drive.DiskController
    let vmName = get_VmNameByDiskController controller
    execute_remote_HyperF_function drive.Server [| "add_VMHardDiskToDrive"; vmName; controller.Name; string drive.Address; vhdPath |]

/// <summary>
/// Connect an emulated Hard disk to a drive in the specified controller.
/// </summary>
let add_VMHardDiskToController (controller:DiskController) address vhdPath =
  if address >= int(controller.Limit) then failwith "The specified Address is out of the controller limit"
  let drive =
    let d = try get_VMDrivesByController controller |> Array.find (fun x -> x.Address = address)
            with _ -> add_VMDiskDriveToController controller address
    if (get_VMDiskByDrive d).IsNone then d
    else failwith "There is already a disk drive in the specified Address"
  add_VMHardDiskToDrive drive vhdPath  


/// <summary>
/// Exports the specified Virtual Machine to the specified path.
/// The path where the VM will be exported can be a network resource.
/// NOTE: Beginning with Windows Server 2008 R2 this method is deprecated.
/// </summary>
let export_VM (vm:VirtualMachine) exportDirectory exportSavedState =
  if isLocal vm.Server then
    LocalOnly.export_VM vm.Name exportDirectory exportSavedState
  else
    execute_remote_HyperF_function vm.Server [| "export_VM"; vm.Name; exportDirectory; string exportSavedState |]


let exportEx_VM (vm:VirtualMachine) exportDirectory copyVmStorage copyVmRuntimeInformation =
  if isLocal vm.Server then
    LocalOnly.exportEx_VM vm.Name exportDirectory copyVmStorage copyVmRuntimeInformation
  else
    execute_remote_HyperF_function vm.Server [| "exportEx_VM"; vm.Name; exportDirectory; string copyVmStorage; string copyVmRuntimeInformation |]


/// <summary>
/// Imports an exported Virtual Machine from the specified path.
/// NOTE: Beginning with Windows Server 2008 R2 this method is deprecated.
/// </summary>
let import_VM server importDirectory generateNewID =
  match server with
  | None -> LocalOnly.import_VM importDirectory generateNewID
  | Some(s) when isLocal s -> LocalOnly.import_VM importDirectory generateNewID
  | Some(s) -> execute_remote_HyperF_function s [| "import_VM"; importDirectory; string generateNewID |]


let importEx_VM server newVmName generateNewID importDirectory createCopy rootDirectoryToCopy =
  match server with
  | None -> LocalOnly.importEx_VM newVmName generateNewID importDirectory createCopy rootDirectoryToCopy
  | Some(s) when isLocal s -> LocalOnly.importEx_VM newVmName generateNewID importDirectory createCopy rootDirectoryToCopy
  | Some(s) -> execute_remote_HyperF_function s [| "importEx_VM"; newVmName; string generateNewID; importDirectory; string createCopy; rootDirectoryToCopy |]



/// <summary>
/// Deletes a Virtual Machine.
/// If the deleteVhd param is True, deletes the VHD files too.
/// Returns a string array which represent the VHD paths not deleted
/// (returns an empty array if all VHD was deleted)
/// </summary>
let delete_VM (vm:VirtualMachine) deleteVhd =
  if isLocal vm.Server then
    LocalOnly.delete_VM vm deleteVhd
  else
    let ret = execute_remote_HyperF_function_withReturn vm.Server [| "delete_VM"; vm.Name; string deleteVhd |]
    (ObjSerializer.deserialize ret) :?> string array

/// <summary>
/// Deletes a Virtual Machine.
/// If the deleteVhd param is True, deletes the VHD files too.
/// Returns a string array which represent the VHD paths not deleted
/// (returns an empty array if all VHD was deleted)
/// </summary>
let delete_VM_byName server vmName deleteVhd =
  let vm = get_VM server vmName
  delete_VM vm deleteVhd


// TODO: add a XmlDoc
//let clone_VMDisk (vmDisk:Disk) destPath =


// TODO: add a XmlDoc
//let clone_VMDisk_byVmName (vmSource:string) (index:int) destPath =

  
/// Clones a Virtual Machine in the local HyperV server
//let clone_VM sourceVm newName memorySize cpuCount =



//
// *************************************
// Other useful and powerful functions
//
  
  
/// Move a Virtual Machine (in any state) from the local HyperV server to another one.
/// The moved VM will be in the suspended state, so if you want to restart it, you should do it manually (with the start_VM function)

//TODO: has to be checked - too much modifies to HyperF could has made this function buggy
(*
let internal move_VM (sourceVm:VirtualMachine) destServer destVhdPath =
  // If the VM to move is running, suspend it first
  let vmState = sourceVm.State
  if vmState <> VMState.Suspended then
    suspend_VM sourceVm
  //-
  // Patch the remote vhd path before moving...
  let template = sprintf @"\\" + destServer + @"\C$\$1"
  let newRemoteVhdPath = System.Text.RegularExpressions.Regex.Replace(destVhdPath, "^[a-zA-Z]:\\\\(.*)", template)
  printfn "newRemoteVhdPath = %s" newRemoteVhdPath
  //-
  // To avoid warning in the import step, we need to remove the NIC switches.
  let list = new System.Collections.Generic.List<Lazy<unit>>()
  let t1 = get_VMNic sourceVm true true
  for t in t1 do
    let srcSwitch = get_VMNicSwitch t
    if srcSwitch.ManagementObject <> null then
      let dstSwitch = get_AllVMSwitch (Some destServer) |> Array.find (fun x -> x.Name = srcSwitch.Name)
      let lz = lazy (
                    let nic = get_VMNicByMACaddress (Some destServer) t.MacAddress
                    let dstVm = get_VMByMACaddress (Some destServer) t.MacAddress
                    set_VMNicSwitch dstVm nic (Some dstSwitch)
               )
      list.Add(lz)
      set_VMNicSwitch sourceVm t None
  //-
  // Now at last we can export the VM
  export_VM sourceVm newRemoteVhdPath
  //-
  // After exported the VM, we can import it in the destination server
  let newRemoteVhdPath = destVhdPath + "\\" + sourceVm.Name
  import_VM (Some destServer) newRemoteVhdPath false
  //-
  // After exported, we need to rebuild the NICs
  for lz in list do lz.Force()
  list.Clear()
  //-
  // Finally, delete the source one!
  delete_VM sourceVm true |> ignore
*)  


(*

let generateNames (pattern:string) =
  let i1 = pattern.IndexOf('$')
  let i2 = pattern.LastIndexOf('$')
  let p = pattern.Substring(i1, i2-i1+1)
  let name = pattern.Split( [|p|], System.StringSplitOptions.None )
  let p = pattern.Substring(i1+1, i2-i1-1)
  let range = p.Split('-')
  let n1 = System.Int32.Parse(range.[0])
  let n2 = System.Int32.Parse(range.[1])
  let format : Printf.StringFormat<int -> string> = 
      match range.[0].Length with
      | 1 -> "%d" | 2 -> "%02d" | 3 -> "%03d" | 4 -> "%04d"
      | 5 -> "%05d" | 6 -> "%06d" | 7 -> "%07d" | 8 -> "%08d"
      | _ -> "%d"
  [| for i = n1 to n2 do yield sprintf format i |]
    |> Array.map (fun x -> sprintf "%s%s%s" name.[0] x name.[1])

*)